using IQIGame.Onigao.Framework;
using IQIGame.Onigao.GamePlay;
using LFloatMath.Math;
using System;
using System.Collections.Generic;

namespace IQIGame.Onigao.Logic
{
    public class Formula
    {
        private static Dictionary<int, Formula> cache = new Dictionary<int, Formula>();

        #region Calculate



        public static T Calculate<T>(int nID, IFormulaObject rSelf = null, IFormulaObject rTarget = null, Skill rSkill = null)
        {
            if (!cache.TryGetValue(nID, out var rFormula))
            {
                var rConfig = TableCenter.formula.Get(nID);
                if (rConfig == null)
                {
                    LogGame.LogError("未找到公式" + nID);
                    return default;
                }

                rFormula = new Formula();
                cache[nID] = rFormula;
                rFormula.Initialize(rConfig.Value);
            }

            rFormula.BindingSkill(rSkill);
            rFormula.Calculate<T>(out var rResult, rSelf, rTarget);
            rFormula.ClearBinding();
            return rResult;
        }

        public static T Calculate<T, T1>(int nID, IFormulaObject rSelf, IFormulaObject rTarget, T1 rParam1)
        {
            if (!cache.TryGetValue(nID, out var rFormula))
            {
                var rConfig = TableCenter.formula.Get(nID);
                if (rConfig == null)
                {
                    LogGame.LogError("未找到公式" + nID);
                    return default;
                }

                rFormula = new Formula();
                cache[nID] = rFormula;
                rFormula.Initialize(rConfig.Value);
            }

            rFormula.BindingDynamic(rParam1);
            rFormula.Calculate<T>(out var rResult, rSelf, rTarget);
            rFormula.ClearBinding();
            return rResult;
        }

        public static T Calculate<T, T1, T2>(int nID, IFormulaObject rSelf, IFormulaObject rTarget, T1 rParam1, T2 rParam2)
        {
            if (!cache.TryGetValue(nID, out var rFormula))
            {
                var rConfig = TableCenter.formula.Get(nID);
                if (rConfig == null)
                {
                    LogGame.LogError("未找到公式" + nID);
                    return default;
                }

                rFormula = new Formula();
                cache[nID] = rFormula;
                rFormula.Initialize(rConfig.Value);
            }

            rFormula.BindingDynamic(rParam1, rParam2);
            rFormula.Calculate<T>(out var rResult, rSelf, rTarget);
            rFormula.ClearBinding();
            return rResult;
        }

        public static T Calculate<T, T1, T2>(int nID, IFormulaObject rSelf, IFormulaObject rTarget, Skill rSkill, T1 rParam1, T2 rParam2)
        {
            if (!cache.TryGetValue(nID, out var rFormula))
            {
                var rConfig = TableCenter.formula.Get(nID);
                if (rConfig == null)
                {
                    LogGame.LogError("未找到公式" + nID);
                    return default;
                }

                rFormula = new Formula();
                cache[nID] = rFormula;
                rFormula.Initialize(rConfig.Value);
            }

            rFormula.BindingDynamic(rParam1, rParam2);
            rFormula.BindingSkill(rSkill);
            rFormula.Calculate<T>(out var rResult, rSelf, rTarget);
            rFormula.ClearBinding();
            return rResult;
        }

        public static T Calculate<T, T1, T2, T3>(int nID, IFormulaObject rSelf, IFormulaObject rTarget, T1 rParam1, T2 rParam2, T3 rParam3)
        {
            if (!cache.TryGetValue(nID, out var rFormula))
            {
                var rConfig = TableCenter.formula.Get(nID);
                if (rConfig == null)
                {
                    LogGame.LogError("未找到公式" + nID);
                    return default;
                }

                rFormula = new Formula();
                cache[nID] = rFormula;
                rFormula.Initialize(rConfig.Value);
            }

            rFormula.BindingDynamic(rParam1, rParam2, rParam3);
            rFormula.Calculate<T>(out var rResult, rSelf, rTarget);
            rFormula.ClearBinding();
            return rResult;
        }

        public static T Calculate<T, T1, T2, T3, T4>(int nID, IFormulaObject rSelf, IFormulaObject rTarget, Skill rSkill, T1 rParam1, T2 rParam2, T3 rParam3, T4 rParam4)
        {
            if (!cache.TryGetValue(nID, out var rFormula))
            {
                var rConfig = TableCenter.formula.Get(nID);
                if (rConfig == null)
                {
                    LogGame.LogError("未找到公式" + nID);
                    return default;
                }

                rFormula = new Formula();
                cache[nID] = rFormula;
                rFormula.Initialize(rConfig.Value);
            }

            rFormula.BindingDynamic(rParam1, rParam2, rParam3, rParam4);
            rFormula.BindingSkill(rSkill);
            rFormula.Calculate<T>(out var rResult, rSelf, rTarget);
            rFormula.ClearBinding();
            return rResult;
        }

        public static T CalculateWithInherit<T>(int nID, Formula rOldFormula, IFormulaObject rSelf = null, IFormulaObject rTarget = null)
        {
            if (rOldFormula != null)
            {
                if (!cache.TryGetValue(nID, out var rFormula))
                {
                    rFormula = new Formula();
                    cache[nID] = rFormula;
                    rFormula.Initialize(TableCenter.formula.Get(nID).Value);
                }

                rFormula.InheritDynamic(rOldFormula);
                rFormula.Calculate<T>(out var rResult, rSelf, rTarget);
                rFormula.ClearBinding();
                return rResult;
            }

            return default;
        }

        #endregion

        public string formulaStr;
        public List<object> rePolish = new List<object>();
        public EFormulaState state = EFormulaState.None;

        public List<FormulaOperandAttr> selfAttr = new List<FormulaOperandAttr>();
        public List<FormulaOperandAttr> targetAttr = new List<FormulaOperandAttr>();

        public Stack<IFormulaOperand> tempStack = new Stack<IFormulaOperand>();

        private List<IFormulaOperand> dynamicOperands = new List<IFormulaOperand>();
        public Skill bindingSkill { get; private set; }

        private int dynamicNum;
        private bool inherit;

        public enum EFormulaState
        {
            None,
            WaitForCalculate,
            Parsing,
            Calculating,
        }

        public Formula Initialize(string formulaStr)
        {
            if (this.state == EFormulaState.Parsing)
            {
                throw new Exception("公式嵌套不允许嵌套自身");
            }

            this.state = EFormulaState.Parsing;
            this.formulaStr = formulaStr;
            if (FormulaHelper.TryParse(this.formulaStr, this))
            {
                this.state = EFormulaState.WaitForCalculate;
                return this;
            }

            throw new Exception($"公式{formulaStr}初始化失败");
        }

        private void BindingSkill(Skill rSkill)
        {
            this.bindingSkill = rSkill;
        }

        private void BindingDynamic<T>(T param1)
        {
            this.dynamicNum = 1;
            if (this.dynamicOperands.Count < this.dynamicNum)
            {
                for (int i = this.dynamicOperands.Count; i < this.dynamicNum; i++)
                {
                    this.dynamicOperands.Add(null);
                }
            }

            this.dynamicOperands[0] = this.CreateDynamicOperand(param1);
        }

        private void BindingDynamic<T1, T2>(T1 param1, T2 param2)
        {
            this.dynamicNum = 2;
            if (this.dynamicOperands.Count < this.dynamicNum)
            {
                for (int i = this.dynamicOperands.Count; i < this.dynamicNum; i++)
                {
                    this.dynamicOperands.Add(null);
                }
            }

            this.dynamicOperands[0] = this.CreateDynamicOperand(param1);
            this.dynamicOperands[1] = this.CreateDynamicOperand(param2);
        }

        private void BindingDynamic<T1, T2, T3>(T1 param1, T2 param2, T3 param3)
        {
            this.dynamicNum = 3;
            if (this.dynamicOperands.Count < this.dynamicNum)
            {
                for (int i = this.dynamicOperands.Count; i < this.dynamicNum; i++)
                {
                    this.dynamicOperands.Add(null);
                }
            }

            this.dynamicOperands[0] = this.CreateDynamicOperand(param1);
            this.dynamicOperands[1] = this.CreateDynamicOperand(param2);
            this.dynamicOperands[2] = this.CreateDynamicOperand(param3);
        }

        private void BindingDynamic<T1, T2, T3, T4>(T1 param1, T2 param2, T3 param3, T4 param4)
        {
            this.dynamicNum = 4;
            if (this.dynamicOperands.Count < this.dynamicNum)
            {
                for (int i = this.dynamicOperands.Count; i < this.dynamicNum; i++)
                {
                    this.dynamicOperands.Add(null);
                }
            }

            this.dynamicOperands[0] = this.CreateDynamicOperand(param1);
            this.dynamicOperands[1] = this.CreateDynamicOperand(param2);
            this.dynamicOperands[2] = this.CreateDynamicOperand(param3);
            this.dynamicOperands[3] = this.CreateDynamicOperand(param4);
        }

        /// <summary>
        /// 继承绑定的数据
        /// </summary>
        /// <param name="rOld"></param>
        private void InheritDynamic(Formula rOld)
        {
            this.inherit = true;
            this.dynamicNum = rOld.dynamicNum;
            this.dynamicOperands.Clear();
            this.dynamicOperands.AddRange(rOld.dynamicOperands);
        }

        public bool TryGetDynamic<T>(int nIndex, out T value, IFormulaObject rSelf, IFormulaObject rTarget)
        {
            if (nIndex >= 0 && nIndex < this.dynamicNum)
            {
                return this.dynamicOperands[nIndex].TryGet(out value, rSelf, rTarget, this);
            }

            value = default;
            return false;
        }

        private void ClearBinding()
        {
            if (!this.inherit)
            {
                for (int i = 0; i < this.dynamicNum; i++)
                {
                    var rOpd = this.dynamicOperands[i];
                    if (rOpd != null)
                    {
                        rOpd.TryFree(true);
                    }

                    this.dynamicOperands[i] = null;
                }
            }

            this.inherit = false;
            this.dynamicNum = 0;
            this.bindingSkill = null;
        }

        private IFormulaOperand CreateDynamicOperand<T>(T param)
        {
            IFormulaOperand rOpd = null;
            switch (param)
            {
                case bool b:
                    rOpd = FormulaHelper.FetchOperand(EFormulaOperandType.Bool, true);
                    rOpd.Set(param);
                    break;
                case LFloat f:
                    rOpd = FormulaHelper.FetchOperand(EFormulaOperandType.Num, true);
                    rOpd.Set(param);
                    break;
                case int i:
                    rOpd = FormulaHelper.FetchOperand(EFormulaOperandType.Num, true);
                    rOpd.Set(new LFloat(i));
                    break;
                case string str:
                    rOpd = FormulaHelper.FetchOperand(EFormulaOperandType.String, true);
                    rOpd.Set(str);
                    break;
                case object obj:
                    rOpd = FormulaHelper.FetchOperand(EFormulaOperandType.Object, true);
                    rOpd.Set(obj);
                    break;
                default:
                    throw new Exception("不支持的动态参数类型，需要拓展" + typeof(T));
            }

            return rOpd;
        }

        public bool Calculate<T>(out T rResult, IFormulaObject rSelf = null, IFormulaObject rTarget = null)
        {
            if (this.state == EFormulaState.Calculating)
            {
                throw new Exception($"公式嵌套不允许嵌套自身 : [{this.formulaStr}]");
            }

            this.state = EFormulaState.Calculating;
            if (this.selfAttr.Count > 0 && rSelf != null)
            {
                var rAttr = rSelf.GetAttr();
                for (int i = 0; i < this.selfAttr.Count; i++)
                {
                    this.selfAttr[i].SetParam(rAttr);
                }
            }

            if (this.targetAttr.Count > 0 && rTarget != null)
            {
                var rAttr = rTarget.GetAttr();
                for (int i = 0; i < this.targetAttr.Count; i++)
                {
                    this.targetAttr[i].SetParam(rAttr);
                }
            }

            var bSuccess = FormulaHelper.Calculate(this, out rResult, rSelf, rTarget);
            this.state = EFormulaState.WaitForCalculate;
            return bSuccess;
        }

        public void Dispose()
        {
            for (int i = 0; i < this.rePolish.Count; i++)
            {
                if (this.rePolish[i] is IFormulaOperand opd)
                {
                    opd.TryFree(true);
                }
                else if (this.rePolish[i] is FormulaOperator opr)
                {
                    opr.Free();
                }
            }

            this.rePolish.Clear();
            this.selfAttr.Clear();
            this.targetAttr.Clear();
            this.state = EFormulaState.None;
        }
    }
}